#include "StdAfx.h"
#include "TerrainMesh.h"
#include "Terrain.h"

#include "TestApplication.h"
#include "D3DRenderer.h"

#include "chunkmanager.h"

using namespace Common;
using namespace ChunkSystem;
using namespace TerrainSystem;

// A structure for our custom vertex type
struct CUSTOMVERTEX
{
	Vector3 vPos;      // The untransformed, 3D position for the vertex
	DWORD	color;        // The vertex color
};

// Our custom FVF, which describes our custom vertex structure
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)

TerrainMesh::TerrainMesh(TerrainBlockChunkItem* pTerrainBlock)
	:ChunkItem::LoadProgressListener(pTerrainBlock),m_bBuiltFlag(false),m_pTerrainBlock(pTerrainBlock),m_pVB(NULL),m_pIB(NULL)
{
	InitializeCriticalSection(&m_CriticalSection);
}


TerrainMesh::~TerrainMesh(void)
{
	this->Unload();

	m_pTerrainBlock->DettachLoadProgressListener(this);

	DeleteCriticalSection(&m_CriticalSection);
}

void TerrainMesh::Load( ChunkSystem::IRenderer* pRenderer )
{
	D3DRenderer* pD3DRenderer = dynamic_cast<D3DRenderer*>(pRenderer);
	if( pD3DRenderer == NULL )
		return;

	EnterCriticalSection(&m_CriticalSection);

	this->_BuildTerrain( pD3DRenderer->GetD3DDevice() );

	LeaveCriticalSection(&m_CriticalSection);

}

void TerrainMesh::Unload( void )
{
	EnterCriticalSection(&m_CriticalSection);

	if( m_pVB )
	{
		m_pVB->Release();
		m_pVB = NULL;
	}
	if( m_pIB )
	{
		m_pIB->Release();
		m_pIB = NULL;
	}

	LeaveCriticalSection(&m_CriticalSection);

}

void TerrainMesh::OnProgressIntervalNotify( uint16 uPercentage )
{
}

void TerrainMesh::Draw( ChunkSystem::IRenderer* pRenderer )
{
	D3DRenderer* pDebugRenderer = dynamic_cast< D3DRenderer* >( pRenderer );
	if( pDebugRenderer == NULL )
		return;

	LPDIRECT3DDEVICE9 pDevice = pDebugRenderer->GetD3DDevice();

	EnterCriticalSection(&m_CriticalSection);
	
	if( !this->_DrawTerrain(pDevice) )
		assert(false);

	LeaveCriticalSection(&m_CriticalSection);
}

bool TerrainMesh::_BuildTerrain( LPDIRECT3DDEVICE9 pDevice )
{
	if( m_pTerrainBlock == NULL || pDevice == NULL)
		return false;

	if( m_bBuiltFlag )
		return true;

	Terrain* pTerrain = m_pTerrainBlock->GetTerrainInstance();
	assert( pTerrain != NULL );

	uint16 uWidth  = 0;
	uint16 uHeight = 0;

	float fUnitSize = pTerrain->GetUnitSize();
	pTerrain->GetExtent( uWidth, uHeight );
	if( uWidth == 0 || uHeight == 0 )
		return true;

	Vector3 vScale = pTerrain->GetScale();
	//and now the width is equal to height
	float fScaledUnitSize = vScale.x;
	float fScaledHeight   = vScale.y;

	if( FAILED( pDevice->CreateVertexBuffer( sizeof(CUSTOMVERTEX) * ( uWidth + 1 ) * ( uHeight + 1 ), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, &m_pVB, NULL) ) )
		return false;

	float fHeight = 0.0f;
	Vector3 vStartPos = pTerrain->GetStartPos();


	CUSTOMVERTEX* pVBData;
	if( FAILED( m_pVB->Lock( 0, sizeof(CUSTOMVERTEX) * ( uWidth + 1 ) * ( uHeight + 1 ), (void**)&pVBData, 0 ) ) )
		return false;
	{
		for( uint16 uIdxY = 0; uIdxY <= uHeight; ++uIdxY )
		{
			for( uint16 uIdxX = 0; uIdxX <= uWidth; ++uIdxX )
			{
				if( !pTerrain->GetHeight( uIdxX, uIdxY, fHeight ) )
					assert(false);

				pVBData[ uIdxY * ( uWidth + 1 ) + uIdxX ].vPos.x = vStartPos.x + fScaledUnitSize * uIdxX;
				pVBData[ uIdxY * ( uWidth + 1 ) + uIdxX ].vPos.z = vStartPos.z + fScaledUnitSize * uIdxY;
				pVBData[ uIdxY * ( uWidth + 1 ) + uIdxX ].vPos.y = vStartPos.y + fScaledHeight * fHeight;

				pVBData[ uIdxY * ( uWidth + 1 ) + uIdxX ].color = 0xffffffff;
			}
		}
	}
	m_pVB->Unlock();

	if( FAILED( pDevice->CreateIndexBuffer( uWidth * uHeight * 6 * sizeof(UINT16) , 0 , D3DFMT_INDEX16 , D3DPOOL_MANAGED , &m_pIB , NULL ) ) )
		return false;

	UINT16* idxTerrain;
	if( FAILED( m_pIB->Lock( 0, uWidth * uHeight * 6 * sizeof(UINT16), (void**)&idxTerrain, 0 ) ) )
		return false;
	{
		for( uint16 uIdxY = 0; uIdxY != uHeight; ++uIdxY )
		{
			for( uint16 uIdxX = 0; uIdxX != uWidth; ++uIdxX )
			{
				if( uIdxX % 2 == 0 )
				{
					idxTerrain[6 * ( uIdxX + uWidth * uIdxY	)    ] = uIdxX + ( uWidth + 1 ) * uIdxY;
					idxTerrain[6 * ( uIdxX + uWidth * uIdxY ) + 1] = uIdxX + ( uWidth + 1 ) * ( uIdxY + 1 ) + 1;
					idxTerrain[6 * ( uIdxX + uWidth * uIdxY ) + 2] = uIdxX + ( uWidth + 1 ) * ( uIdxY + 1 );

					idxTerrain[6 * ( uIdxX + uWidth * uIdxY ) + 3] = uIdxX + ( uWidth + 1 ) * uIdxY;
					idxTerrain[6 * ( uIdxX + uWidth * uIdxY ) + 4] = uIdxX + ( uWidth + 1 ) * uIdxY + 1;
					idxTerrain[6 * ( uIdxX + uWidth * uIdxY ) + 5] = uIdxX + ( uWidth + 1 ) * ( uIdxY + 1 ) + 1;
				}
				else
				{
					idxTerrain[6 * ( uIdxX + uWidth * uIdxY	)    ] = uIdxX + ( uWidth + 1 ) * uIdxY;
					idxTerrain[6 * ( uIdxX + uWidth * uIdxY ) + 1] = uIdxX + ( uWidth + 1 ) * uIdxY + 1;
					idxTerrain[6 * ( uIdxX + uWidth * uIdxY ) + 2] = uIdxX + ( uWidth + 1 ) * ( uIdxY + 1 );

					idxTerrain[6 * ( uIdxX + uWidth * uIdxY ) +	3] = uIdxX + ( uWidth + 1 ) * uIdxY + 1;
					idxTerrain[6 * ( uIdxX + uWidth * uIdxY ) + 4] = uIdxX + ( uWidth + 1 ) * ( uIdxY + 1 ) + 1;
					idxTerrain[6 * ( uIdxX + uWidth * uIdxY ) + 5] = uIdxX + ( uWidth + 1 ) * ( uIdxY + 1 );
				}
			}
		}
	}
	m_pIB->Unlock();

	m_bBuiltFlag = true;

	return true;
}

bool TerrainMesh::_DrawTerrain( LPDIRECT3DDEVICE9 pDevice )
{
	if( pDevice == NULL )
		return true;

	if( m_pIB == NULL || m_pVB == NULL )
		return true;

	if( FAILED( pDevice->SetFVF(D3DFVF_CUSTOMVERTEX) ) )
		assert( false );

	if( FAILED( pDevice->SetStreamSource(0, m_pVB, 0, sizeof(CUSTOMVERTEX) ) ) ) 
		assert( false );

	if( FAILED( pDevice->SetIndices(m_pIB) ) )
		assert( false );

	Terrain* pTerrain = m_pTerrainBlock->GetTerrainInstance();
	assert( pTerrain != NULL );

	uint16 uWidth  = 0;
	uint16 uHeight = 0;

	pTerrain->GetExtent( uWidth, uHeight );
	if( uWidth == 0 || uHeight == 0 )
		return true;

	pDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME );

	if( FAILED( pDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, (uWidth + 1) * (uHeight + 1), 0, uWidth * uHeight * 2 ) ) )
		return false;

	pDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );

	return true;
}
